#include "filesys/buffer_cache.h"
#include "filesys/filesys.h"
#include "threads/malloc.h"
#include "devices/disk.h"
#include "threads/interrupt.h"
#include <string.h>
#include <stdio.h>
#include <round.h>

/* List that contains the entries of the buffer cache,
   at the beggining the the entries are created dinamically
   but once a entry */
static struct list buffer_cache;

/* Track the number of buffer cache entries */
static uint32_t entries;

/* Lock on buffer cache to ensure syncronization */
static struct lock bfc_lock;

/* Current victim in the eviction policy, we are thinking
   about clock algorithm */
static struct list_elem *cur_victim;

static struct bfc_entry * select_victim (void);

/* Initialize the buffer cache's components */
void
init_buffer_cache ()
  {
    list_init (&buffer_cache);
    lock_init (&bfc_lock);
    entries = 0;
    cur_victim = NULL;
  }
  
/* Returns the disk sector that contains byte offset POS within
   INODE.
   Returns -1 if INODE does not contain data for a byte at offset
   POS. */
static disk_sector_t
get_sector (struct inode *inode, off_t pos) 
{
  ASSERT (inode != NULL);
  
  /*if (pos < inode->data.length)
    return inode->data.start + pos / DISK_SECTOR_SIZE;
  else*/
    return -1;
}

/* Look for a victim using clock algorithm */
static struct bfc_entry *
select_victim () 
  {
    struct bfc_entry *cure;
    bool cond;
    
    while(true)
      { // Finds a victim
        cond = false;
        if(cur_victim==NULL)
          cur_victim = list_begin (&buffer_cache);
          
        cure = list_entry (cur_victim, struct bfc_entry, elem);
        
        lock_acquire(&cure->lock);
        cond = (cure->rds == 0 &&  cure->evictable);
        lock_release (&cure->lock);
        
        if(cond)
          {
            if(cure->accessed) // Give a second chance
                cure->accessed = false;
            else // the current entry is a good choice, so it becomes our victim
                break;
          }
        
        // Advance the current possible victim if this is not evictable or if it
        // accessed
        if(list_next (cur_victim)==list_end (&buffer_cache))
          cur_victim = list_begin (&buffer_cache);
        else
          cur_victim = list_next (cur_victim);
      }
      
    // Once the victim has been chosen advance the current victim
    if(list_next (cur_victim)==list_end (&buffer_cache))
      cur_victim = list_begin (&buffer_cache);
    else
      cur_victim = list_next (cur_victim);
      
    // This entry is not evictable until it reaches a consistent state
    //cure->evictable = false;
    
    return cure;
  }

/* Initialize a buffer cache entry */
struct bfc_entry *
buffer_cache_get_entry (struct inode *inode, off_t offset)
  {
    ASSERT (offset % DISK_SECTOR_SIZE == 0);
    
    struct bfc_entry *bfce;
    disk_sector_t sector_idx;
    
    if(entries < 1000)//MAX_BFC_SIZE)
      {
        bfce = (struct bfc_entry *) malloc (sizeof(struct bfc_entry));
        if(bfce == NULL)
          {
            printf ("Fuck getting an entry of bfc!\n");
            return NULL;
          }
          
        // IS here an apropiated place for sych?
        //lock_acquire (&bfc_lock);
        bfce->evictable = true;
        entries++;
        list_push_front (&buffer_cache, &bfce->elem);
        //lock_release (&bfc_lock);  
        
        bfce->addr = malloc (DISK_SECTOR_SIZE);
        if(bfce == NULL)
          {
            printf ("Fuck getting the memory for bfce!\n");
            free (bfce);
            return NULL;
          }
          
        // Reads the needed information for this entry from filesys_disk
        sector_idx = get_sector (inode, offset);
        
        //printf ("BFC FSD: %p, SEC_IDX: %d\n", filesys_disk, sector_idx);
        disk_read (filesys_disk, sector_idx, bfce->addr);
        
        //printf ("BFC Sector: %d\n", sector_idx);
        
        bfce->inode = inode;
        bfce->offset = offset;
        bfce->dirty = false;
        bfce->accessed = false;
        
        bfce->rds = 0;
        lock_init (&bfce->lock);
      }
    else
      { // This case is not necessary for first test cases
        //lock_acquire (&bfc_lock);
        bfce = select_victim ();
        
        // Change This with a direct writing on filesys_disk
        if(bfce->dirty)
          {
            sector_idx = get_sector (bfce->inode, bfce->offset);
            disk_write (filesys_disk, sector_idx, bfce->addr);
          }
        
        //Reads the needed information for this entry from filesys_disk
        sector_idx = get_sector (inode, offset);
        disk_read (filesys_disk, sector_idx, bfce->addr);
        
        //lock_release (&bfc_lock);
        
        bfce->inode = inode;
        bfce->offset = offset;
        bfce->dirty = false;
        bfce->accessed = false;
        bfce->rds = 0;
      }
      
    return bfce;
  }
  

/* Look for the given inode in the buffer cache, the offset
   is rouned down to make the search */
struct bfc_entry *
buffer_cache_look_up (struct inode *inode, off_t offset)
  {
    struct list_elem *e;
    struct bfc_entry *cure;
    
    for(e=list_begin (&buffer_cache); e!=list_end (&buffer_cache); e=list_next (e))
      {
        cure = list_entry (e, struct bfc_entry, elem);
        
        if(cure->inode==inode && cure->offset==offset)
        {
          disk_read (filesys_disk, get_sector (inode, offset), cure->addr);
          return cure;
        }
      }
    
    return NULL;
  }
  
void
buffer_cache_write_back ()
  {
    //enum intr_level  prev;
    struct list_elem *e;
    struct bfc_entry *cure;
    
    disk_sector_t sector_idx;
    
    //prev = intr_disable ();
    for(e=list_begin (&buffer_cache); e!=list_end (&buffer_cache); e=list_next(e))
      {
        cure = list_entry (e, struct bfc_entry, elem);
        
        cure->accessed = false;
        if(cure->dirty)
          {
            //inode_write_at (cure->inode, cure->addr, cure->bytes, cure->offset);
            cure->dirty = false;
            sector_idx = get_sector (cure->inode, cure->offset);
            //if(sector_idx != -1)
              disk_write (filesys_disk, sector_idx, cure->addr);
          }
      }
    //intr_set_level (prev);
  }

void
buffer_cache_flush ()
  {
    struct list_elem *e;
    struct bfc_entry *cure;
    
    while(!list_empty (&buffer_cache))
      {
        e = list_pop_front (&buffer_cache);
        cure = list_entry (e, struct bfc_entry, elem);
        free (cure->addr);
        free (cure);
      }
  }




















